Skip to content

fix(locks): early contributor-cap close joins the per-PR actuation lock namespace#8813

Merged
JSONbored merged 1 commit into
mainfrom
fix/cap-close-actuation-lock
Jul 26, 2026
Merged

fix(locks): early contributor-cap close joins the per-PR actuation lock namespace#8813
JSONbored merged 1 commit into
mainfrom
fix/cap-close-actuation-lock

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Round-2 audit fix (#8805): the #7284 early cap-close ran under only the per-author cap lock, outside the per-PR actuation namespace transient-locks.ts documents as covering every mutating PR pass — a concurrent sweep pass for the same PR could act simultaneously.

Changes

  • src/queue/processors.ts — the cap-close's executeAgentMaintenanceActions section additionally try-claims claimPrActuationLock, deferring cleanly on contention (return false; the end-of-pipeline cap check + next tick are the existing backstops). Nesting is author-outer → PR-inner — the opposite of the executor's pre-merge re-check — which is deadlock-free by construction since both namespaces use non-blocking try-claims (cross-order contention = mutual clean defer, documented at the claim site).
  • The sibling marker-comment TOCTOU the audit noted (maybePostVisualFollowupComment, unlocked) is deliberately untouched: it self-heals via canonical-comment dedup on the next call, and lock-skipping there would drop a comment that has no retry backstop — the worse trade. Noted here per the issue's scope line.

Test plan

  • npm run typecheck — clean
  • New contention test mirroring the author-lock sibling: a held pr-actuation-lock defers the early close (no close PATCH) and the normal pipeline falls through
  • queue-3 suite 149/149
  • Changed hunks: 0 uncovered statements/branches

Closes #8805

…ck namespace (#8805)

maybeCloseForContributorCapOnOpen executed its close under only the
per-(repo, author) cap lock — outside the "one shared lock namespace covering
every mutating PR pass" contract (transient-locks.ts) — so a sweep-fanned
agent-regate-pr job holding the PR-actuation lock could plan/execute a
different action for the SAME PR concurrently with the cap close.

The execution section now also try-claims the per-PR actuation lock, nested
author-outer → PR-inner. That is the OPPOSITE nesting of the executor's
pre-merge cap re-check (PR-outer → author-inner, #7284) — safe regardless:
both locks are non-blocking try-claims, so cross-order contention degrades to
both passes deferring cleanly (the end-of-pipeline cap check and the next
tick are the backstops), never a blocking-wait deadlock. Documented at the
claim site. Contention test mirrors the existing author-lock sibling: the
close defers (no PATCH), the normal pipeline falls through.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 26, 2026
@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.26%. Comparing base (11fb8ee) to head (2431817).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8813      +/-   ##
==========================================
- Coverage   93.81%   92.26%   -1.55%     
==========================================
  Files         797      797              
  Lines       79608    79612       +4     
  Branches    24127    24128       +1     
==========================================
- Hits        74686    73457    -1229     
- Misses       3556     5090    +1534     
+ Partials     1366     1065     -301     
Flag Coverage Δ
backend 92.94% <100.00%> (-2.16%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/queue/processors.ts 95.69% <100.00%> (+<0.01%) ⬆️

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 26, 2026
@loopover-orb

loopover-orb Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-26 07:30:32 UTC

2 files · 1 AI reviewer · no blockers · readiness 100/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This closes the audited gap where the #7284 early contributor-cap close executed outside the per-PR actuation lock namespace by having it try-claim `claimPrActuationLock` before calling `executeAgentMaintenanceActions`, deferring cleanly (return false) on contention and releasing in a `finally` alongside the existing outer contributor-cap-lock release. The nesting is author-outer/PR-inner, which the description correctly argues is deadlock-free against the executor's opposite PR-outer/author-inner ordering because both are non-blocking try-claims that degrade to mutual clean defer rather than a blocking wait. The new queue-3 test exercises the contended path end-to-end (holds the PR lock, asserts no close PATCH fires, and asserts the pipeline still falls through to AI review), which is the right verification for this change.

Nits — 3 non-blocking
  • src/queue/processors.ts: the sibling `maybePostVisualFollowupComment` TOCTOU is left intentionally unlocked per the description's stated tradeoff — worth a one-line pointer comment at that call site too, not just here, so a future reader doesn't rediscover the same question independently.
  • test/unit/queue-3.test.ts: the new test hardcodes the lock cache key string `pr-actuation-lock:feat(signals): wire issue quality reports through snapshot + REST + MCP + opportunities #56` rather than deriving it from an exported key-builder helper, so it will silently stop testing contention (false negative) if the key format in transient-locks.ts ever changes without this literal being updated.
  • Consider exporting the lock key builder from transient-locks.ts (if not already) so tests construct contention keys the same way production code does, guarding against key-format drift.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #8805
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 13 registered-repo PR(s), 13 merged, 246 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 13 PR(s), 246 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Partially addressed
The diff adds a try-claim of the per-PR actuation lock around executeAgentMaintenanceActions in maybeCloseForContributorCapOnOpen with correct author-outer/PR-inner nesting and a contention test verifying clean defer, addressing the primary race described in the issue. However, the issue explicitly asked to include the marker-comment TOCTOU sibling (maybePostVisualFollowupComment) 'in the same loc

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 13 PR(s), 246 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 26, 2026
@JSONbored
JSONbored merged commit af80c4c into main Jul 26, 2026
7 checks passed
@JSONbored
JSONbored deleted the fix/cap-close-actuation-lock branch July 26, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(locks): contributor-cap close runs outside the per-PR actuation lock namespace — sibling passes can race the same PR

1 participant